一個 JavaScript 函式庫,專門用來發送 HTTP 請求(像 GET、POST)到伺服器,並接收回應。可以用在 Vue、React、Node.js 等各種環境。
讓你不用自己寫一堆 XMLHttpRequest 或 fetch,語法更直覺、功能更多。
axios.get()
→ 拿資料axios.post()
→ 傳送資料1.語法簡單
import axios from 'axios'
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(res => console.log(res.data))
2.支援 Promise / async-await
更方便處理非同步。
const res = await axios.get('/api/data')
console.log(res.data)
只要一行就能拿到資料。
3.自動轉換 JSON
不需要手動 .json(),直接就拿到物件。
4.全域設定
可以設 baseURL、timeout、headers,一次設定全域適用。
5.攔截器
請求送出前、回應回來後可以攔截,常用來統一處理「帶 Token」或「錯誤提示」。
6.瀏覽器 & Node.js 都能用
前端後端共通。
安裝
npm install axios
建立一支可重用的通訊器(建議做法)
在 src/lib/http.js
先創建一個 axios 實例,未來所有 API 都走這裡。
import axios from 'axios'
// 從環境變數帶入後端位址(.env 用 VITE_ 前綴)
const http = axios.create({
baseURL: import.meta.env.VITE_API_BASE || 'https://jsonplaceholder.typicode.com',
timeout: 10000
})
// 請求攔截器:帶 token、統一 header
http.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
// 回應攔截器:只回傳 data,錯誤統一拋出
http.interceptors.response.use(
res => res.data,
err => {
// 你可以在這裡做 401 轉跳登入、彈錯誤訊息等
return Promise.reject(err)
}
)
export default http
.env
範例(根目錄):
VITE_API_BASE=https://jsonplaceholder.typicode.com
在 Vue 裡實作一個「外星通訊站」:src/views/CommCenter.vue
以下用 Composition API 示範列表讀取、單筆詳細、載入/錯誤狀態、重新整理與取消請求。
<template>
<main class="wrap">
<h1>🛰️ 外星通訊站</h1>
<section class="toolbar">
<button @click="fetchList" :disabled="loading">重新整理</button>
<span v-if="loading">📡 訊號連線中…</span>
<span v-if="error" class="err">❌ {{ error }}</span>
</section>
<ul class="list" v-if="messages.length">
<li v-for="m in messages" :key="m.id" @click="open(m.id)">
<strong>#{{ m.id }}</strong> {{ m.title }}
</li>
</ul>
<p v-else-if="!loading && !error">目前沒有訊息。</p>
<article v-if="detail" class="detail">
<h2>訊息 #{{ detail.id }}</h2>
<p>{{ detail.body }}</p>
<button @click="detail = null">關閉</button>
</article>
</main>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import http from '../lib/http'
import axios from 'axios'
const messages = ref([])
const detail = ref(null)
const loading = ref(false)
const error = ref('')
let cancel // 用於取消請求
async function fetchList() {
error.value = ''
detail.value = null
loading.value = true
// 取消上一個未完成請求(如果有)
if (cancel) cancel('Use new request')
const controller = new AbortController()
cancel = (reason) => controller.abort(reason)
try {
// 以 JSONPlaceholder 當作示範 API
messages.value = await http.get('/posts', { signal: controller.signal })
} catch (err) {
if (axios.isCancel(err)) return
error.value = parseAxiosError(err)
} finally {
loading.value = false
}
}
async function open(id) {
error.value = ''
detail.value = null
loading.value = true
const controller = new AbortController()
try {
detail.value = await http.get(`/posts/${id}`, { signal: controller.signal })
} catch (err) {
if (axios.isCancel(err)) return
error.value = parseAxiosError(err)
} finally {
loading.value = false
}
}
function parseAxiosError(err) {
// 友善錯誤訊息(可依專案客製)
if (err.response) {
return `伺服器錯誤(${err.response.status})`
} else if (err.request) {
return '網路連線異常或伺服器無回應'
} else {
return err.message || '未知錯誤'
}
}
onMounted(fetchList)
onBeforeUnmount(() => cancel && cancel('Component unmounted'))
</script>
<style scoped>
.wrap { max-width: 860px; margin: 40px auto; padding: 0 16px; font: 16px/1.6 ui-sans-serif, system-ui; }
.toolbar { display:flex; gap:12px; align-items:center; margin-bottom:12px; }
.err { color:#ef4444; }
.list { list-style:none; padding:0; }
.list li { padding:8px 10px; border:1px solid #e5e7eb; border-radius:8px; margin-bottom:8px; cursor:pointer; }
.list li:hover { background:#f8fafc; }
.detail { border:1px solid #e5e7eb; border-radius:12px; padding:12px; margin-top:12px; background:#fff; }
</style>
src/router/index.js
你可以把這頁註冊到路由中,例如 routes: [{ path: '/comm', component: CommCenter }]
,或放在既有頁面中引用。
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Planet from '../views/Planet.vue'
// 子頁面(相對路徑,沒有前導斜線)
import PlanetOverview from '../views/planet/Overview.vue'
import PlanetMoons from '../views/planet/Moons.vue'
import PlanetResearch from '../views/planet/Research.vue'
import NotFound from '../views/NotFound.vue'
import CommCenter from '../views/CommCenter.vue' // 外星通訊站
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', name: 'home', component: Home },
{
path: '/planet/:slug',
name: 'planet',
component: Planet,
props: route => ({ slug: route.params.slug }),
children: [
{ path: '', redirect: { name: 'planet-overview' } },
{ path: 'overview', name: 'planet-overview', component: PlanetOverview, props: true },
{ path: 'moons', name: 'planet-moons', component: PlanetMoons, props: true },
{ path: 'research', name: 'planet-research', component: PlanetResearch, props: true },
// ⚠ 這裡不要放 /comm
]
},
// ⬇️ 頂層獨立路由
{ path: '/comm', name: 'comm', component: CommCenter },
{ path: '/:pathMatch(.*)*', name: '404', component: NotFound }
],
scrollBehavior: () => ({ top: 0 })
})
export default router
App.vue
<template>
<header class="nav">
<RouterLink to="/" class="brand">🚀 Orbit Coders</RouterLink>
<RouterLink to="/comm">🛰️ 外星通訊站</RouterLink>
</header>
<RouterView />
</template>
<style scoped>
.nav { display:flex; gap:16px; align-items:center; padding:12px 16px; border-bottom:1px solid #e2e8f0; background:#ffffff; position:sticky; top:0; z-index:10; }
.brand { text-decoration:none; font-weight:700; color:#0f172a; }
</style>
乾淨的 API 模組(可選、推薦)
專案長大後,把每個領域的 API 拆檔:src/apis/posts.js
import http from '../lib/http'
export function listPosts(params) {
return http.get('/posts', { params })
}
export function getPost(id) {
return http.get(`/posts/${id}`)
}
export function createPost(payload) {
return http.post('/posts', payload)
}
export function updatePost(id, payload) {
return http.put(`/posts/${id}`, payload)
}
export function deletePost(id) {
return http.delete(`/posts/${id}`)
}
元件就只管呼叫 listPosts()
、getPost()
,不碰 axios 細節 → 減少耦合、好測試。
明天我們將進一步了解Vue 與本地儲存(localStorage)!
參考資源
https://vuejs.org/guide
https://www.runoob.com/vue3